package dk.mirasola.systemtraining.bridgewidgets.shared.model.distributionfiltertree.shape;

import com.google.gwt.user.client.rpc.IsSerializable;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.Hand;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.Suit;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


public class ShapeType implements IsSerializable {
    public static final ShapeType SHAPE_TYPE_4333 = new ShapeType(new int[]{4, 3, 3, 3});
    public static final ShapeType SHAPE_TYPE_4432 = new ShapeType(new int[]{4, 4, 3, 2});
    public static final ShapeType SHAPE_TYPE_5332 = new ShapeType(new int[]{5, 3, 3, 2});
    public static final ShapeType SHAPE_TYPE_6322 = new ShapeType(new int[]{6, 3, 2, 2});

    public static boolean validate(List<ShapeElement> shapeElements) {
        checkParameter(shapeElements);

        int min = 0;
        int max = 0;
        for (ShapeElement shapeElement : shapeElements) {
            if (shapeElement == null || shapeElement instanceof IntervalShapeElement) {
                return false;
            } else if (shapeElement instanceof ExactShapeElement) {
                ExactShapeElement exact = (ExactShapeElement) shapeElement;
                min += exact.getCardsCount();
                max += exact.getCardsCount();
            } else if (shapeElement instanceof WildcardShapeElement) {
                max += 13;
            } else {
                throw new RuntimeException("unknown shape element: " + shapeElement);
            }
        }

        return min <= 13 && max >= 13;
    }

    private static void checkParameter(List<ShapeElement> shapeElements) {
        if (shapeElements == null) {
            throw new NullPointerException();
        }

        if (shapeElements.size() != 4) {
            throw new IllegalArgumentException("exactly 4 shape-element most be provided");
        }
    }

    public static ShapeType create(List<ShapeElement> shapeElements, boolean validate) {

        if (validate && !validate(shapeElements)) {
            throw new InvalidShapeTypeException();
        } else {
            checkParameter(shapeElements);
        }

        List<ShapeElement> copy = new ArrayList<ShapeElement>(shapeElements);
        Collections.sort(shapeElements, new Comparator<ShapeElement>() {
            @Override
            public int compare(ShapeElement shapeElement1, ShapeElement shapeElement2) {
                if (shapeElement1 instanceof WildcardShapeElement) {
                    return Integer.MAX_VALUE;
                } else if (shapeElement2 instanceof WildcardShapeElement) {
                    return Integer.MIN_VALUE;
                } else if (shapeElement1 instanceof ExactShapeElement && shapeElement2 instanceof ExactShapeElement) {
                    int cardsCount1 = ((ExactShapeElement) shapeElement1).getCardsCount();
                    int cardsCount2 = ((ExactShapeElement) shapeElement2).getCardsCount();
                    if (cardsCount1 < cardsCount2)
                        return 1;
                    else if (cardsCount1 == cardsCount2)
                        return 0;
                    else
                        return -1;
                }

                throw new RuntimeException("huh");
            }
        });

        return new ShapeType(copy);
    }

    private List<ShapeElement> shapeElements;

    // needed for serialization
    ShapeType() {
    }

    private ShapeType(List<ShapeElement> shapeElements) {
        this.shapeElements = shapeElements;
    }

    private ShapeType(int[] exactCardCounts) {
        this.shapeElements = new ArrayList<ShapeElement>(4);
        for (int exactCardCount : exactCardCounts) {
            this.shapeElements.add(ExactShapeElement.create(exactCardCount, false));
        }
    }

    public List<ShapeElement> getShapeElements() {
        return new ArrayList<ShapeElement>(shapeElements);
    }

    private int[] getCardsCountsSortedDesc(Hand hand) {
        int[] cc = new int[4];
        int i = 0;
        for (Suit suit : Suit.values()) {
            cc[i] = hand.getCardsCount(suit);
            i++;
        }
        Arrays.sort(cc);
        return new int[]{cc[3], cc[2], cc[1], cc[0]};
    }

    public boolean matches(Hand hand) {
        int[] shape = getCardsCountsSortedDesc(hand);
        List<ExactShapeElement> exactElements = getExactElementsSortedDesc();
        if (exactElements.size() == 4) {
            for (int i = 0; i < shape.length; i++) {
                if (!exactElements.get(i).match(shape[i]))
                    return false;
            }

            return true;
        } else if (exactElements.size() == 3) {
            boolean firstMatched = false;
            boolean secondMatched = false;
            for (int i = 0; i < shape.length; i++) {
                if (secondMatched) {
                    if (exactElements.get(2).match(shape[i])) {
                        return true;
                    }
                } else if (firstMatched) {
                    if (exactElements.get(1).match(shape[i])) {
                        secondMatched = true;
                    }
                } else {
                    if (exactElements.get(0).match(shape[i])) {
                        firstMatched = true;
                        continue;
                    }
                }
                if (i == 1 && !firstMatched && !secondMatched)
                    return false;
                if (i == 2 && firstMatched && !secondMatched)
                    return false;
            }
            return false;
        } else if (exactElements.size() == 2) {
            boolean firstMatched = false;
            for (int i = 0; i < shape.length; i++) {
                if (firstMatched) {
                    if (exactElements.get(1).match(shape[i])) {
                        return true;
                    }
                } else {
                    if (exactElements.get(0).match(shape[i])) {
                        firstMatched = true;
                        continue;
                    }
                }
                if (i == 2 && !firstMatched)
                    return false;
            }
            return false;
        } else if (exactElements.size() == 1) {
            for (int cardsCount : shape) {
                if (exactElements.get(0).match(cardsCount))
                    return true;
            }
            return false;
        } else if (exactElements.size() == 0) {
            // all wildcards
            return true;
        }
        throw new RuntimeException("huh");
    }

    private List<ExactShapeElement> getExactElementsSortedDesc() {
        List<ExactShapeElement> exactElements = new ArrayList<ExactShapeElement>();
        for (ShapeElement element : shapeElements) {
            if (element instanceof ExactShapeElement) {
                exactElements.add((ExactShapeElement) element);
            }
        }
        Collections.sort(exactElements, new Comparator<ExactShapeElement>() {
            public int compare(ExactShapeElement exactShapeElement1,
                               ExactShapeElement exactShapeElement2) {
                int cardsCount1 = exactShapeElement1.getCardsCount();
                int cardsCount2 = exactShapeElement2.getCardsCount();
                if (cardsCount1 < cardsCount2)
                    return 1;
                else if (cardsCount1 == cardsCount2)
                    return 0;
                else
                    return -1;
            }
        });
        return exactElements;
    }

    public String toString() {
        StringBuffer buffer = new StringBuffer();

        buffer.append("any");
        for (ShapeElement shapeElement : shapeElements) {
            buffer.append(shapeElement);
            buffer.append("-");
        }

        return buffer.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ShapeType shapeType = (ShapeType) o;
        return shapeElements.equals(shapeType.shapeElements);
    }

    @Override
    public int hashCode() {
        return shapeElements.hashCode();
    }
}